﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using System;

namespace Bunbyx.game
{
    public class CollisionDetector
    {
        // --------------------------------------------------------------------
        //  Constants
        // --------------------------------------------------------------------

        private const int MASK_WIDTH = 10;

        // --------------------------------------------------------------------
        //  Delegates
        // --------------------------------------------------------------------

        public delegate void CollisionDetected(Sprite collider);

        // --------------------------------------------------------------------
        //  Private attributes
        // --------------------------------------------------------------------

        private Dictionary<int, Sprite> backgroundTrack;
        private BunbyX bunbyX;
        private List<Sprite> sprites;
        private event CollisionDetected collisionHandler;

        // --------------------------------------------------------------------
        //  Constructor
        // --------------------------------------------------------------------

        public CollisionDetector(Dictionary<int, Sprite> backgroundTrack, BunbyX bunbyX, List<Sprite> sprites, CollisionDetected collisionHandler)
        {
            this.backgroundTrack = backgroundTrack;
            this.bunbyX = bunbyX;
            this.sprites = sprites;
            this.collisionHandler = collisionHandler;
        }

        // --------------------------------------------------------------------
        //  Public methods
        // --------------------------------------------------------------------
        public void DetectCollisions(GraphicsDeviceManager graphics, SpriteBatch spriteBatch)
        {
            detectGroundCollisions(graphics, spriteBatch);
            detectSpritesCollisions();
        }

        // --------------------------------------------------------------------
        //  Private methods
        // --------------------------------------------------------------------

        private void detectGroundCollisions(GraphicsDeviceManager graphics, SpriteBatch spriteBatch)
        {
            // Cache variables
            int posX = (int)bunbyX.Position.X;
            int posY = (int)bunbyX.Position.Y;
            int width = (int)bunbyX.Size.Width;
            int height = (int)bunbyX.Size.Height;

            // Create collision masks
            Rectangle maskRight = new Rectangle(posX + width - MASK_WIDTH, posY + 2 * MASK_WIDTH, MASK_WIDTH, height - 4 * MASK_WIDTH);
            Rectangle maskLeft = new Rectangle(posX, posY + 2 * MASK_WIDTH, MASK_WIDTH, height - 4 * MASK_WIDTH);
            Rectangle maskDown = new Rectangle(posX + 2 * MASK_WIDTH, posY + height - MASK_WIDTH, width - 4 * MASK_WIDTH, MASK_WIDTH);
            Rectangle maskUp = new Rectangle(posX + 2 * MASK_WIDTH, posY, width - 4 * MASK_WIDTH, MASK_WIDTH);

            // Detect collisions with masks
            bool collisionRight = collisionInThisZone(graphics, spriteBatch, maskRight);
            bool collisionLeft = collisionInThisZone(graphics, spriteBatch, maskLeft);
            bool collisionDown = collisionInThisZone(graphics, spriteBatch, maskDown);
            bool collisionUp = collisionInThisZone(graphics, spriteBatch, maskUp);

            // Down collision
            if (collisionUp && collisionDown)
            {
                bunbyX.LoadLastPosition();
            }
            else
            {
                if (collisionDown && !collisionUp)
                {
                    bool isBurried;
                    bunbyX.OnTheGround();
                    bunbyX.SetBlockedBot(true);

                    do
                    {
                        Rectangle maskBurry = new Rectangle(maskDown.X, maskDown.Y, maskDown.Width, maskDown.Height - 1);
                        isBurried = collisionInThisZone(graphics, spriteBatch, maskBurry);

                        if (isBurried)
                        {
                            bunbyX.Move(0, -1);
                            posY = (int)bunbyX.Position.Y;
                            maskDown.Y = posY + height - MASK_WIDTH;
                        }
                    } while (isBurried);
                }
                else
                {
                    bunbyX.InTheAir();
                    bunbyX.SetBlockedBot(false);
                }

                // Up collision
                if (collisionUp && !collisionDown)
                {
                    bool isBurried;
                    bunbyX.SetBlockedTop(true);

                    do
                    {
                        Rectangle maskBurry = new Rectangle(maskUp.X, maskUp.Y + 1, maskUp.Width, maskUp.Height - 1);
                        isBurried = collisionInThisZone(graphics, spriteBatch, maskBurry);

                        if (isBurried)
                        {
                            bunbyX.Move(0, 1);
                            posY = (int)bunbyX.Position.Y;
                            maskUp.Y = posY;
                        }
                    } while (isBurried);
                }
                else
                {
                    bunbyX.SetBlockedTop(false);
                }

                // Right collision
                if (collisionRight && !collisionLeft)
                {
                    bool isBurried;
                    bunbyX.SetBlockedRight(true);

                    do
                    {
                        Rectangle maskBurry = new Rectangle(maskRight.X, maskRight.Y, maskRight.Width - 1, maskRight.Height);
                        isBurried = collisionInThisZone(graphics, spriteBatch, maskBurry);

                        if (isBurried)
                        {
                            bunbyX.Move(-1, 0);
                            posX = (int)bunbyX.Position.X;
                            maskRight.X = posX + width - MASK_WIDTH;
                        }
                    } while (isBurried);
                }
                else
                {
                    bunbyX.SetBlockedRight(false);
                }

                // Left collision
                if (collisionLeft && !collisionRight)
                {
                    bool isBurried;
                    bunbyX.SetBlockedLeft(true);

                    do
                    {
                        Rectangle maskBurry = new Rectangle(maskLeft.X + 1, maskLeft.Y, maskLeft.Width - 1, maskLeft.Height);
                        isBurried = collisionInThisZone(graphics, spriteBatch, maskBurry);

                        if (isBurried)
                        {
                            bunbyX.Move(1, 0);
                            posX = (int)bunbyX.Position.X;
                            maskLeft.X = posX;
                        }
                    } while (isBurried);
                }
                else
                {
                    bunbyX.SetBlockedLeft(false);
                }
            }
        }

        private bool collisionInThisZone(GraphicsDeviceManager graphics, SpriteBatch spriteBatch, Rectangle mask)
        {
            float x1 = mask.X;
            float x2 = x1 + mask.Width;
            float y1 = mask.Y;
            float y2 = y1 + mask.Height;

            foreach (Sprite trackPart in backgroundTrack.Values)
            {
                float tx1 = trackPart.Position.X;
                float tx2 = tx1 + trackPart.Size.Width;
                float ty1 = trackPart.Position.Y;
                float ty2 = ty1 + trackPart.Size.Height;

                if (((x1 > tx1 && x1 < tx2) || (x2 > tx1 && x2 < tx2)) && ((y1 > ty1 && y1 < ty2) || (y2 > ty1 && y2 < ty2)))
                {
                    Texture2D collisionTexture = createCollisionTexture(graphics, spriteBatch, trackPart, mask);
                    Color[] colorMatrics = new Color[mask.Width * mask.Height];
                    collisionTexture.GetData<Color>(colorMatrics);

                    foreach (Color color in colorMatrics)
                    {
                        if (color == Color.Black)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        private Texture2D createCollisionTexture(GraphicsDeviceManager graphics, SpriteBatch spriteBatch, Sprite trackPart, Rectangle mask)
        {
            RenderTarget2D backgroundTrackRender = new RenderTarget2D(graphics.GraphicsDevice, mask.Width, mask.Height);
            graphics.GraphicsDevice.SetRenderTarget(backgroundTrackRender);
            graphics.GraphicsDevice.Clear(ClearOptions.Target, Color.Red, 0, 0);

            spriteBatch.Begin();

            Rectangle correctedMask = new Rectangle((int)((mask.X - trackPart.Position.X) / trackPart.Scale),
                                                    (int)((mask.Y - trackPart.Position.Y) / trackPart.Scale),
                                                    (int)(mask.Width / trackPart.Scale),
                                                    (int)(mask.Height / trackPart.Scale));

            spriteBatch.Draw(
                trackPart.SpriteTexture,
                Vector2.Zero,
                correctedMask,
                Color.White,
                0.0f,
                Vector2.Zero,
                trackPart.Scale,
                SpriteEffects.None,
                0);

            spriteBatch.End();

            graphics.GraphicsDevice.SetRenderTarget(null);

            return backgroundTrackRender;
        }

        private void detectSpritesCollisions()
        {
            foreach (Sprite sprite in sprites)
            {
                if (sprite != bunbyX && distanceTo(sprite) < sprite.Radius)
                {
                    collisionHandler(sprite);
                }
            }
        }

        private float distanceTo(Sprite sprite)
        {
            float dx = bunbyX.Center.X - sprite.Center.X;
            float dy = bunbyX.Center.Y - sprite.Center.Y;

            return (float)Math.Sqrt(dx * dx + dy * dy);
        }
    }
}
